باسمه تعالی
فصل ۱۸ - ارثبری
یکی دیگر از ويژگیهای زبان که با معمولا در کنار شیگرایی میآید ارثبری است. ارثبری امکانی است که کلاسی را بعنوان یک نسخه تغییر یافته کلاس دیگر ایجاد کرد. در این فصل من ارثبری را به کمک کلاسهایی برای نشاندادن کارتهای بازی، مجموعهای از کارتها و یک دست پوکر، ارائه میدهم.
اگر شما با پوکر آشنایی ندارید به آدرس میتوانید در این باره در http://en.wikipedia.org/wiki/Poker بخوانید اما نیازی به آن ندارید. من هر آنچه برای انجام تمرینات به آن نیاز داشته باشید را برایتان خواهم گفت.
کدهای مثالهای انی فصل در آدرس http://thinkpython2.com/code/Card.py در دسترس است.
#### 18.1 اشیاء کارتها
در یک دست ۵۲ کارت وجود دارند و هریک از آنها به یکی از دستههای چهارگانه تعلق دارند. هر دسته مجموعهای از ۱۳ کارت مرتب هست. هریک از این دستهها خشت، پیک، دل و گشنیز است. ترتیب دستها تک،۲، ۳، ۴، ۵، ۶، ۷، ۸، ۹، ۱۰، سرباز، بی بی، شاه است. بسته به بازی که شما با کارتها انجام میدهید، ممکن است تک از شاه بالاتر باشد یا از دو کمتر باشد.
اگر ما بخواهیم یک شی برای یک بازی تعریف کنید، مشخص است که دو ویژگی رتبه و دسته در آنها وجود دارد. اما نوع این ویژگیها به این وضوح نیست. یکی از راههای ممکن استفاده از رشتههایی که شامل کلماتی مانند «پیک» برای دستهها و «بی بی» برای رتبههاست. یکی از مشکلات این پیادهسازی این است که مقایسه این مقادیر به منظور بدست آوردن رتبه برتر یا دسته است.
یک راه جایگزین این است که رتبهها و دستهها را کدگذاری کنیم. در این حوزه «کدگذاری» به معنای تعریف یک نگاشت میان رتبهها و دستهها است. این نحوه کدگذاریبه این معنا نیست که کدها مخفی است (به آن حالت «رمزنگاری» میگویند).
به عنوان مثال، این جدول نشان میدهد که هر دسته چه ارتباطی با مقادیر صحیح دارد:
پیک -> ۳
دل -> ۲
خشت -> ۱
گشنیز -> ۰
این کدها مقایسه کارتها را آسانتر میکند. بخاطر اینکه دستهها بالاتر اعداد بالاتری دارند، مقایسه دستهها به کمک اعداد امکانپذیر است.
نگاشت رتبهها تقریبا واضح است. هریک از رتبههای عدد به خود آن اعداد صحبح نگاشته میشود و برای کارتهای غیر عدد میتوان نوشت:
سرباز -> ۱۱
بی بی -> ۱۲
شاه -> ۱۳
دلیل استفاده من از نماد -> متمایز بودن این نگاشتها از کد پایتون است. آنها بخشی از طراحی نرمافزار هستند، اما بصورت واضح در کد حضور ندارند.
تعریف کلاس Card شبیه زیر است:
طبق معمول مقادیر آرگومانهای تابع init اختیاری هستند. کارت پیشفرض ۲ گیشنیز است.
برای ساختن کارت، شما Card را با آرگومان های دسته و رتبه کارتی که میخواهید فراخوانی میکنید.
#### 18.2 ویژگیهای کلاس
به منظور پرینت کردن اشیاء کارت بصورت خوانا، ما نیاز به نگاشتی از کدهای صحیح به متنهای دستهها و رتبهها داریم. راه طبیعی این کار استفاده از لیست از رشتهها است. ما این لیستها را به عنوان ویژگیهای اشیا است:
متغیرهایی مثل suit_names و rank_names که داخل کلاس تعریف شده ولی خارج تمام متدهاست به عنوان ویژگیها شناخته میشود. دلیل این نامگذاری ارتباط این متغیرها با یک شی کلاس است.
این واژه آنها را از متغیرهایی مثل suit و rank که ویژگیهای شی نامیده میشوند متفاوت میکند. دلیل استفاده از این نام به این دلیل است که آنها با یک نمونه خاص مرتبط هستند.
هر دو نوع ویژگی با استفاده از دات قابل دسترسی است. به عنوان مثال در __str__، متغیر self یک شی از نوع Card است و self.rank مقدار رتبه آن است. بطور مشابه، Card یک شی ازا این کلاس است و Card.rank_names لیست رشتهها وابسته به کلاس است.
هر کارت suit و rank خودش را دارد، اما تنها یک کپی suit_names و rand_names وجود دارد.
در مجموع، عبارت Card.ran_names[sefl.rank] به معنای «استفاده از ویژگی rank از شی self بوده به عنوان اندیسی از لیست rank_names از کلاس Crad هست که باعث انتخاب رشته مورد نظر میشود.»
اولین المان rand_names مقدارش None است زیرا کارتی با رتبه 0 وجود ندارد. با استفاده از None به عنوان یک جای خالی، ما یک نگاشت خوب خواهیم داشت که اندیس 2 به رشته '2' و به همین صورت برای سایر رشتهها نگاشته خواهند شد. برای جلوگیری از استفاده از این ترفند، میتوان از یک لغتنامه به جای لیست استفاده کرد.
با متدهای که ما تاکنون داریم، ما میتوانیم یک کارت را پرینت کنیم.
در شکل 18.1 یک دایگارم از کلاس Card و یک نمونه از Card است. Card یک شی کلاس هست. نوع آن هم از نوع type است. card1 یک نمونه از Card است پس نوعش Card است. برای ذخیره سازی فضا، من محتوای لیستهای suit_names و rank_names را نکشیده ام.
#### 18.3 مقایسه کارتها
برای انواع توکار، اپراتورهای مقایسهای (<, >, == و ...) وجود داشته که مقایسه مقادیر را انجام داده و مشخص میکند که آیای یکی از دیگری بزگتر، کوچکتر یا مساوی است. برای انواع تعریف شده توسط برنامهنویس ما میتوانیم اپراتورهای توکار را با نوشتن تابعی به اسم __lt__ تغییر دهیم. این تابع به معنای «کمتر از» است.
__lt__ دو پارامتر self و other گرفته در صورتی که قطعا کمتر از دیگری باشد True و در غیر اینصورت False برمیگرداند.
ترتیب دقیق کارتها مشخص نیست. به عنوان مثال کدام یک از کارتها ۳ گشنیز از ۲ خشت بالاتر است؟ یکی رتبه بالاتری داشته و دیگری دسته بالاتری دارد. به منظور مقایسه کارتها شما بایستی تصمیم بگیریدکه کدام یک از مقادیر رتبه و دسته بالاتر است.
با این تصمیم میتوانیم تابع __lt__ را بنویسیم:
شما میتوانید این را بصورت خلاصه تر با استفاده مقایسه چندتاییها بنویسید.
به عنوان تمرین، متد __lt__ را برای اشیا کلاس Time بازنویسی کنید. شما میتوانید از مقایسه چندتایی استفاده کند همچنین میتوانید از مقایسه اعداد صحیح استفاده کنید.
#### 18.4 دستها
اکنون که ما کارتها را داریم، قدم بعدی تعریف دستهاست. از آنجایی که یک دست از کارتها تشکیل شده است، طبیعی است که یک دست لیستی از کارتها را به عنوان ویژگی خود داشته باشد.
تعریف کلاس پیشرو تعریف کلاس دست است. متد init ویژگی cards را ساخته و آنرا با 52 کارت پر میکند.
ساده ترین راه پر کردن یک دست استفاده از حلقههای تو در تو است. حلقه خارجی دستهها را زا ۰ تا ۳ میشمارد. حلقه داخلی رتبههای ۱ تا ۱۳ را میشمارد. در هر گردش یک کار با دسته و رتبه فعلی تولید شده و به انتهای self.cards اضافه میشودپ.
#### 18.5 پرینت کردن دست
این یک پیادهسازی متد __str__ برای دست است:
این متد راه بهینهای برای تجمیع یک رشته بزرگ را به نمایش گذاشته است: ساخت یک لیست از رشتهها و استفاده از متد join روی آنها. تابع توکار str متد __str__ هریک از کارتها را فرخوانی میکند و نمایش رشتهای آنها را باز میگرداند.
از آنجایی که ما تابع join را روی کاراکتر خط جدید فرواخوانی کردهایم، متن کارتها توسط این کاراکتر از یکدیگر جدا میشوند. این نمونه ای از خروجی است:
با وجودی که نتایج در ۵۲ خط به نمایش گذاشته شده است، اما این تنها یک رشته است که حاوی کاراکتر خط جدید است.
#### 18.6 اضافه، حذف و مرتبسازی
برای پخش کردن کارتها، نیاز به متدهایی برای حذف کردن و برگرداندن یک کارت از یک دست است. با استفاده از متد pop روی لیستها راه راحتی برای اینکار وجود دارد.
از آنجایی که pop آخرین کارت را ازدست حذف میکند، ما از انتهای دست، کارتها را پخش میکنیم.
برای اضافه کردن یک کارت جدید میتوانیم از متد append لیست استفاده کنیم.
متدهایی مثل این که از یک متد دیگر بدون انجام عملیات خاصی استفاده میکند، بعضی اوقات روکش(veneer) نامیده میشود. این کنایه از نجاری آمده است که روکش یک لایه نازک چوب با کیفیت است که روی چوب ارزانتر کشیده شده تا ظاهر بهتری داشته باشد.
در وضعیت فعلی، add_card یک متد «نازک» است که یکی از عملیات لیست را به زبان مناسب برای دست بیان میدارد. این باعث بهبود ظاهر یا واسط پیادهسازی میشود.
به عنوان یک مثال دیگر، ما میتوانیم متدی به نام shuffle برای دست بنویسیم که متد shuffle از ماژول random استفاده کند:
فراموش نکنید که پکیج random را import کنید.
به عنوان تمرین یک متد به نام sort برای دست بنویسید که کارتهای یک دست را مرتب میکند. sort از متد __lt__ برای مشخص کردن ترتیب کارتها استفاده می کند.
#### 18.7 ارثبری
ارثبری ویژگی است که یک کلاس جدید را به عنوان یک نسخه تغییر یافته کلاس فعلی تعریف کنیم. به عنوان مثال، میخواهیم کلاسی برای مشخص کردن «دست بازیکن» به معنای کارتهایی است که یک بازیکن دارد، داشته باشیم. یک «دست بازیکن» بسیار شبیه دست است: هر دو از یک مجموعه کارت درست شده اند، هر دو عملیاتی مشابه اضافه و حذف کارتها دارند.
یک دست بازیکن همچنین با یک دست تفاوتهایی دارند. عملیات خاصی برروی دست بازیکن وجود دارد که برای یک دست معنی ندارد. به عنوان مثال، در پوکر ما ممکن است دو دست را با همدیگر مقایسه کنیم.در بریج(bridge) ممکن است بخواهیم ارزش یک دست را محاسبه کرده تا بتوانیم یک شرط ببندیم.
ارتباط بین کلاسها - شبیه اما متفاوت - باعث میشود به ارثبری برسیم. برای تعریف یک کلاس جدید که از یک کلاس فعلی ارث میبرد، شما اسم کلاس موجود را درون پرانتز میگذارید:
این تعریف بیان میدارد که Hand از Deck ارث میبرد. این بدین معناست که ما متوانیم متدهایی مانند pop_card و add_card برای دست بازیکن و دست فراخوانی کنیم.
وقتی کلاس جدید از یک کلاس موجود ارث میبرد، کلاس موجود والد و کلاس جدید فرزند نامیده میشود.
در این مثال کلاس Hand متد __init__ را از کلاس Deck به ارث میبرد اما این دقیقا آن چیزی نیست که ما بخواهیم: در عوض میخواهیم بجای پر کردن دست بازیکن با ۵۲ کارت میخواهیم که متد init برای دست بازیکن باید مقدار اولیه cards را یک لیست خالی بدهد.
اگر ما یک متد init برای کلاس hand فرآهم کنیم، این متد جایگزین متد همنامش در کلاس deck میشود:
وقتی شما یک دست بازیکن جدید میسازید: پایتون متد init را فراخوانی میکند، نه متدی به همین نام در Deck
متدهای دیگر از Deck به ارث برده شده است، پس ما میتوانیم از pop_card و add_card برای کار با کارتها استفاده کنیم.
مرحله بعدی این است که این کد را درون یک متد کپسوله کنیم و به آنرا move_cards بنامیم:
متد move_cards دو آرگومان گرفته، یک دست بازیکن و تعداد کارتهایی که باید پخش شود. این هر دو آرگومان self و hand را تغییر داده و none بازمیگرداند.
در برخی از بازیها، کارتها از دست یک بازیکن به دست بازیکن دیگری انتقال مییابد، یا از یک دست بازیکن به دست اصلی بازمیگردد. شما میتوانید از move_cards برای همه این عملیات استفاده کنید: self میتوانید Deck یا Hand باشد و فارغ از اینکه اسم کلاس چیست hand میتواند بجای Deck مورد استفاده قرار گیرد.
ارث بری یک ویژگی کاربردی است. برخی از برنامهها بدون استفاده از ارث بری ممکن است کارهای تکرار شونده باشد اما با استفاده از ارث بری میتواند به ظرافت بیشتری نوشته شود. ارث بری میتواند باعث استفاده مجدد از کدهای نوشته شده شود، همچنین شما میتوانید کدهای کلاسهای والد را بدون نیاز به تغییر کلاسهای فرزند عوض کنید. در برخی حالات، ساختار ارثبری ساختار طبیعی مساله را منعکس میکند تا طراحی انجام شده قابل فهمتر شود.
از سوی دیگر، ارثبری میتواند خوانایی برنامهها را سختتر کند. وقتی یک متد فرواخوانی شود، در برخی موارد مشخص نیست که تعریف متد را بیابیم. کدهای مرتبط با موضوع در ماژولها مختلفی وجود دارد. همچنین بسیاری از کارهایی را که میتوان با ارث بری انجام داد میتوان به همان خوبی یا حتی بهتر بدون استفاده از ارثبری انجام داد.
#### 18.8 نمودارهای کلاس
تا کنون دیاگرام های stack که وضعیت برنامه را نمایش میدهد، و دیاگرامهای اشیاء نشاندهنده ویژگیهای اشیاء و مقادیر این ویژگیها هستند. این دیاگرامها تصویری از به اجرا درآمدن نرمافزار را نشان میدهند. این دیاگرامها با اجرای برنامه تغییر میکند.
آنها حاوی جزئیات زیادی هستند و برای برخی کارکردها جزئیات بیش از حدی دارند. دیاگرام کلاس یک نمایش انتزاعی از ساختار برنامه است. بجای نشاندادن اشیاء مجزا، این دیاگرام کلاسها و ارتباطات بین آنها را نشان میدهد.
چندین نوع ارتباطات بین کلاسها وجود دارد:
- اشیاء یک کلاس ممکن است حاوی ارجاعی به یک شیء دیگر از کلاس دیگری باشد. به عنوان مثال، هر چهارضلعی rectangle حاوی ارجاعی به یک نقطه point است و یک دست حاوی ارجاعاتی به کارتهاست. این نوع ارتباط دارای has-a نام دارد مثل «چهار ضلعی دارای نقطه است»
- یک کلاس ممکن است از یک کلاس دیگر ارث ببرد. این ارتباط یک is-a نامیده میشود مثل «یک دست بازیکن یک دست است»
- یک کلاس ممکن است به کلاس دیگری وابسته باشد همانند اینکه این کلاس یک شی از کلاس دیگری را به عنوان پارامتر میگیرد یا اینکه از اشیاء موجود در کلاس دیگر برای انجام محاسباتی استفاده میکند. این ارتباطات وابستگی نامیده میشود.
یک دیاگرام کلاس یک نمایش تصویری از این ارتباطات است. به عنوان مثال شکل زیر ارتباط بین card و deck و hand نمایش میدهد.
-- --
فلش با مثلث توخالی نشاندهنده رابط is-a است. پس با توجه به شکل Hand از Deck ارثبری دارد.
فلش استاندارد نشاندهنده رابط has-a است که دراین شکل Deck حاوی ارجاعاتی به card است.
ستاره (*) کنار فلشها بیانگر تعدد است. این نشان میدهد که یک Deck چند کارت دارد. تعداد میتواند یک عدد مانند ۵۲ یا یک بازه مانند ۲..۷ یا ستاره است. ستاره نشانده میدهد که یک دست میتواند هر تعداد کارت داشته باشد.
در این دیاگرام هیچ ارتباط وابستگی وجود ندارد. آنها معمولا به وسیله یک فلش خط چنین نمایش داده میشوند. همچنین اگر تعداد زیادی وابستگی وجود داشته باشد ممکن است کشیده نشوند.
دیاگرام دقیق تر ممکن است نشانده که Deck حاولی یک list از کارتها باشد اما معمولا انواع توکار مانند لیست و dict معمولا در دیاگرام کلاس آورده نمیشوند.
#### 18.9 عیبیابی
ارثبری ممکن است عملیات عیبیابی را مشکل تر کند زیرا هنگام فرواخوانی یک متد از یک شی، ممکن است تشخیص اینکه کدامیک از متدها فرواخوانی میشود مشکل باشد.
فرض کنید که در حال توسعه تابعی هستیم که با اشیا Hand کار میکند. شما میخواهید که این تابع با تمامی انواع دست بازیکن مانند دست پوکر یا دست بریج و … کار کند. اگر شما در این تابع متد shuffle را فراخوانی کنید ممکن است نسخه تعریف شده در کلاس deck فرواخوانی شود، اما اگر یکی از زیرکلاسها این متد را جایگزین کنند شما از نسخه جایگزین شده استفاده خواهید کرد. این ویژگی معمولا خوب است اما ممکن است گیج کننده باشد.
هرگاه که شما نسبت به جریان اجرا برنامه شک دارید، سادهترین راه استفاده از عبارت print در ابتدای متد مربوطه است. اگر متد Deck.shuffle عبارتی مثل Running Deck.shuffle چاپ کند هنگام اجرای برنامه ردپایی از جریان اجرای برنامه در اختیار میگذارد.
بصورت جایگزین شما میتوانید از این تابع استفاده کنید که یک شی و رشته اسم متد را به عنوان آرگومان دریافت کرده و کلاسی را که این متد در آن تعریف شده است را باز میگرداند:
این یک مثال استفاده از آن است:
پس متد shuffle برای Hande از نسخه تعریف شده در Deck استفاده میکند.
تابع find_defining_class از متد mro برای گرفتن لیستی از اشیاء کلاس(نوعها) به منظور جستجوی متد استفاده میکند.MRO مخفف Method resolution order یا ترتیب تشخیص متد است. این تابع لیستی از کلاسها است که پایتون برای تشخیص آن متد مورد استفاده قرار میدهد.
به این پیشنهاد طراحی توجه کنید: وقتی یک متد را جایگزین میکنید، واسط متد جدید بایستی شبیه قدیمی باشد. این متد بایستی همان پارامترها را گرفته و نوع مقدار بازگشتی یکی باشد. همچنین بایستی پیششرطها و پسشرطها را رعایت کند. اگر شما از این قانون تبعیت کنید درخواهید یافت که هر تابعی که برای کار با اشیاء کلاس پدر (همانند Deck) طراحی شده اند، میتوانند روی کلاسهای فرزند مانند Hand و PockerHand نیز کار کنند.
اگر این قانون را زیر بگذارید (که به آن اصل جایگزینی لیسکوو نیز گفته میشود) کد شما همانند یک خانه پوشالی (متاسفانه) فروخواهد ریخت.
#### 18.10 کپسوله سازی داده
فصلهای قبل یک نقشه توسعه را نشان میدادند که ممکن است به آن «طراحی شیء گرا» بگوییم. ما اشیائی را که مورد نیاز بود -مانند Point، Rectangle و Time- مشخص کرده و کلاسهایی برای نمایش آنها تعریف کردیم. در هر یک از این حالات یک ارتباط میان شی و موجودیتی در دنیای واقعی(یا حداقل در دنیای ریاضی) وجود داشت.
اما گاهی اینکه به چه اشیاءای نیاز بوده و آن اشیاء چگونه با هم تعامل میکنند به این وضوح نیست. در این حالت شما نیاز به یک نقشه توسعه متفاوت دارید. همانگونه که ما با استفاده از کپسوله سازی و تعمیم واسط توابع را یافتیم، میتوانیم واسط کلاسها را با استفاده از کپسوله سازی داده بیابیم.
تحلیل مارکوف از بخش 13.8 یک مثال خوب است. اگر شما کدی که نوشته ام را از آدرس http://thinkpython2.com/code/markov.py دانلود کنید خواهید دید که دو متغیر عمومی -suffix_map و prefix- وجود دارد که توسط توابع متعددی خوانده و نوشته میشوند.
بخاطر اینکه این متغیرها عمومی هستند، ما تنها میتوانیم تحلیل را برای یکبار انجام دهیم. اگر دو متن را بخوانیم، پیشوندها و پسوندها به همان ساختار داده اضافه میشوند(که تحلیل برای یک متن تولید شده خواهد بود)
برای اجرا چند تحلیل و جدا نگهداشتن آنها، ما میتوانیم که هریک از آنلایزها را در یک شیء کپسوله کنیم. بدین صورت که:
در قدم بعد ما توابع را تبدیل به متد می کنیم. به عنوان مثال این تابع process_word است:
تبدیل برنامه بدین شکل (تبدیل طراحی برنامه بدون تغییر رفتار برنامه) یک مثل دیگر از refactoring است(فصل 4.7 را ببینید)
این مثال پیشنهادی برای نقشه توسعه و طراحی اشیاء و متدها ارائه میدهد:
- از نوشتن توابع که از متغیرهای عمومی برای نوشتن و خواند استفاده میکنند شروع کنید.
- وقتی کارکرد برنامه به درستی انجام میشد، به دنبال ارتباط میان متیغرهای عمومی و توابع که از آنها استفاده میکنند بگردید
- متغیرهای مرتبط را به عنوان ویژگیها شیء کپسوله کنید
- توابع وابسته را به عنوان متدها یک کلاس در نظر بگیرید.
به عنوان تمرین کد مارکف من را از http://thinkpython2.com/code/markov.py دانلود کنید و با استفاده از مراحل بالا متغیرهای عمومی را به عنوان ویژگیهای شیء کپسوله کنید و کلاس جدید به اسم Markov بسازید. جواب در این لینک : http://thinkpython2.com/code/Markov.py
#### 18.11 واژه نامه
کدگذاری: نشاندادن مجموعه از مقادیر به کمک مجموعه دیگری از مقادیر بگونهای که نگاشتی بین آنها وجود داشته باشد
ویژگی کلاس: ویژگی که مرتبط با یک شی کلاس باشد. این ویژگیها درون کلاس و خارج متدهای آن تعریف میشوند.
ویژگیهای نمونه: ویژگیهایی که به یک نمونه از یک کلاس وابسته است.
روکش: یک متد یا تابع که بدون انجام محاسبات زیاد واسط متفاوتی برای یک تابع دیگر فراهم میکند.
ارثبری: توانایی تعریف یک کلاس به عنوان نسخه تغییر یافته یک کلاس موجود.
کلاس والد: کلاسی که کلاس فرزند از آن ارث میبرد.
کلاس فرزند: کلاسی جدیدی که با ارث بردن از یک کلاس موجود ساخته میشود. این کلاس همچنین زیر کلاس نیز نامیده میشود.
رابطه is-a: رابطه میان کلاس فرزند و والدش را گویند
رابطه has-a: رابطه میان دو کلاس وقتی نمونههایی یک کلاس حاوی ارجاعی به نمونههای یک کلاس دیگر باشد.
وابستگی: رابطه بین دو کلاس وقتی که یک کلاس از نمونه یک کلاس دیگر استفاده میکند ولی آنها را به عنوان یک ویژگی ذخیره نمیکند.
نمودار کلاس: نموداری که کلاسهای یک برنامه و ارتباط میان آنها را به نمایش میگذارد.
چندتایی: یک علامت در نمودار کلاس که برای رابطه has-a نشان میدهد چند ارجاع به نمونه کلاس دیگر وجود دارد.
کپسوله سازی داده: یک روش توسعه نرمافزار که شامل ساختن یک نمونه اولیه به کمک متغیرهای عمومی است و نسخه نهایی که متغیرهای عمومی به ویژگیها نمونه تبدیل میشوند.
#### 8.12 تمرینات
تمرین 18.1: برای برنامه زیر یک نمودار کلاس UML رسم کنید که این کلاسها و ارتباط میان آنها را نمایش دهد.
تمرین 18.2 یک متد به نام deal_hands برای Deck بنویسید که دو پارامتر بگیرد، تعداد دست بازیکن و تعداد کارتهای یک دست بازیکن. این متد باید تعداد کافی شی از نوع دست بازیکن ساخته و تعداد کافی کارت به آنها اختصاص دهد و در نهایت لیستی از Hand بازگرداند
تمرین 18.3 لیست زیر حالات ممکن برای دست بازیکن در پوکر است. به ترتیب ارزش این حالات بیشتر شده و احتمال وقوع آنها کمتر میشود
- جفت: دو کارت از یک رتبه باشند
- دو جفت: دو جفت کارت از یک رتبه باشند
- سه : سه کارت از یک رتبه باشند
- ردیف: پنج کارت پشت سر هم باشند(تک میتواند بیشترین یا کمترین باشد پس تک-۱-۲-۳-۴-۵ یک ردیف هست و ۱۰-سرباز-بی بی-شاه-تک هم یک ردیف است.)
- رنگ: همه کارتها از یک نوع باشند
- فول: وقتی سه کارت از یک رتبه و دو کارت از رتبه دیگر باشند
- کاره: وقتی چهار کارت از یک رتبه باشند
- استریت فلش: وقتی پنج کارت از یک نوع بوده و پشت سر هم ردیف باشند.
هدف این تمرینها تخمین احتمال یکی از این دستهاست
- این فایلها را از http: // thinkpython2. com/ code دانلود کنید:\ Card.py: نسخه کامل کلاسها Card, Deck, Hand است\ PokerHand.py: که حاوی پیاده سازی ناقص کلاس دست پوکر و کدهایی برای تست آن است.
- اگر شما PokerHand.py را اجرا کنید، این فایل یک بازیک پوکر با ۷ کارت در دست بازیکننان تقسیم کرده و چک میکند که آیا هریک از آنها حاوی یک رنگ است یا نه. این کد را قبل از ادامه دادن با دقت بخوانید
- متدهای به PokerHand.py با نامهای has_pair و has_twopair و … اضافه کنید. این متدها در صورت وجود حالت مورد نظر True و در صورت عدم وجود حالت False بازمیگرداند.
- متدی به نام classify بنویسید که با ارزش ترین حالت یک دست پوکر را مشخص کند. به عنوان مثال یک دست ۷ کارته ممکنه است یک جفت و رنگ داشته باشد. اما این تابع رنگ را باز میگرداند.
- وقتی مطمئن شدید که دستهبندی شما کار میکند مرحله بعدی تخمین احتمال حالتهای مختلف است. یک تابع در PokerHand.py بنویسید که دست کارتها را برزده و به دستهایی تقسیم کرده، آنها را دسته بندی کرده و تعداد رخدادهای دسته ها مختلف را میشمارد.
- جدولی از دستهها و احتمالات آنها چاپ کنید. تعداد اجرای برنامهتان را بیشتر و بیشتر کنید تا جایی که احتمالهای به دست آمده تقریبا به عدد قابل قبولی همگرا شود. نتایجتان را با این لینک مقایسه کنید. http://en.wikipedia.org/wiki/Hand_rankings
جواب http://thinkpython2.com/code/PokerHandSoln.py